import 'package:collection/collection.dart' show IterableExtension;
import 'package:my_flutter_core/constants/user_role.dart';
import 'package:my_flutter_core/mixins/action_log_mixin.dart';
import 'package:my_flutter_core/models/role.dart';
import 'package:my_flutter_core/utils/date_time_utils.dart';
import 'package:my_flutter_core/utils/format_utils.dart';

class User with ActionLogMixin {
  User({
    this.accountGroupId = '',
    this.id = '',
    this.username = '',
    this.password = '',
    this.fullName = '',
    this.avatarUrl = '',
    this.description = '',
    this.isAccountGroupCreator = false,
    this.enabled = true,
    this.isAccountGroupAdmin = false,
    this.fullRoles,
    DateTime? dateCreated,
    DateTime? lastUpdated,
  }) {
    fullRoles ??= [];

    this.dateCreated = dateCreated;
    this.lastUpdated = lastUpdated;
  }

  /// We never retrieves user password from server or any other place.
  /// That's why we don't need the [password] property in [fromJson] method.
  factory User.fromJson(Map<String, dynamic> json) {
    final user = User(
      accountGroupId: json['accountGroupId']?.toString(),
      id: json['id']?.toString(),
      username: json['username']?.toString(),
      fullName: json['fullName']?.toString(),
      avatarUrl: json['avatarUrl']?.toString(),
      description: json['description']?.toString(),
      isAccountGroupCreator: tryToConvert<bool>(
        json['isAccountGroupCreator'],
        (p) => p as bool,
      ),
      enabled: tryToConvert<bool>(json['enabled'], (p) => p as bool),
      dateCreated: tryToConvert<DateTime>(
        json['dateCreated'],
        (p) => parseDate(p),
      ),
      lastUpdated: tryToConvert<DateTime>(
        json['lastUpdated'],
        (p) => parseDate(p),
      ),
      fullRoles: tryToConvert<List<Role>>(
        json['fullRoles'],
        (p) => (p as List).map((p) => Role.fromJson(p)).toList(),
        defaultValue: <Role>[],
      ),
    );

    // If fullRoles exist then it will be used to check if the user is account group admin.
    // Otherwise json['isAccountGroupAdmin'] will be used instead.
    user.isAccountGroupAdmin = _checkIsAccountGroupAdmin(
      user.fullRoles!,
      json['isAccountGroupAdmin'],
    );

    return user;
  }

  static const usernameMaxLength = 11;
  static const passwordMinLength = 6;
  static const passwordMaxLength = 20;
  static const fullNameMaxLength = 11;
  static const avatarUrlMaxLength = 200;
  static const descriptionMaxLength = 100;

  String? accountGroupId;
  String? id;
  String? username;
  String? password;
  String? fullName;
  String? avatarUrl;
  String? description;
  bool? isAccountGroupCreator;
  bool? enabled;
  bool? isAccountGroupAdmin;
  List<Role>? fullRoles;

  bool get isViewerOnly {
    return fullRoles?.length == 1 &&
            fullRoles?.first.authority == UserRole.accountGroupViewer
        ? true
        : false;
  }

  /// For adding user we need to send the user info to server which includes [password].
  Map<String, dynamic> toJson() {
    return {
      'accountGroupId': accountGroupId,
      'id': id,
      'username': username,
      'password': password,
      'fullName': fullName,
      'avatarUrl': avatarUrl,
      'description': description,
      'isAccountGroupCreator': isAccountGroupCreator,
      'enabled': enabled,
      'isAccountGroupAdmin': isAccountGroupAdmin,
      'fullRoles': fullRoles?.map((p) => p.toJson()).toList(),
    };
  }

  /// Checks if the input [user] has the same values.
  bool equals(User? user) {
    if (user == null) {
      return false;
    }

    // Checks on each necessary property.
    var hasSameValues =
        accountGroupId == user.accountGroupId &&
        id == user.id &&
        username == user.username &&
        password == user.password &&
        fullName == user.fullName &&
        avatarUrl == user.avatarUrl &&
        description == user.description &&
        isAccountGroupCreator == user.isAccountGroupCreator &&
        enabled == user.enabled &&
        isAccountGroupAdmin == user.isAccountGroupAdmin;

    // Checks the full roles count.
    if (fullRoles?.length != user.fullRoles?.length) {
      hasSameValues = false;
    }

    // If the full role count matches then checks each role.
    if (fullRoles != null &&
        user.fullRoles != null &&
        fullRoles!.length == user.fullRoles!.length) {
      for (var i = 0; i < fullRoles!.length; i++) {
        // If current role cannot be found in user.fullRoles that means they are not identical.
        // Uses orElse parameter to slient 'No Element' error if no match found.
        if (user.fullRoles!.firstWhereOrNull((p) => fullRoles![i].id == p.id) ==
            null) {
          hasSameValues = false;

          break;
        }
      }
    }

    return hasSameValues;
  }

  /// Clones a [User] with same [username], [fullName], [avatarUrl], [description], [enabled] values.
  User clone() {
    return User(
      accountGroupId: accountGroupId,
      id: id,
      username: username,
      password: password,
      fullName: fullName,
      avatarUrl: avatarUrl,
      description: description,
      isAccountGroupCreator: isAccountGroupCreator,
      enabled: enabled,
      isAccountGroupAdmin: isAccountGroupAdmin,
      fullRoles: fullRoles!.map((fullRole) => fullRole.clone()).toList(),
    );
  }

  /// Checks if user is account group admin.
  static bool _checkIsAccountGroupAdmin(
    List<Role> fullRoles,
    dynamic isAccountGroupAdmin,
  ) {
    if (fullRoles.isNotEmpty == true) {
      return fullRoles.firstWhereOrNull(
            (p) => p.authority!.contains(UserRole.accountGroupAdmin) == true,
          ) !=
          null;
    } else if (isAccountGroupAdmin != null) {
      return isAccountGroupAdmin;
    } else {
      return false;
    }
  }
}
